#include <iostream>

#include "asserts.hpp"
#include "battle.hpp"
#include "draw_scenario.hpp"
#include "filesystem.hpp"
#include "foreach.hpp"
#include "formatter.hpp"
#include "formula_game_logic.hpp"
#include "player.hpp"
#include "scenario.hpp"
#include "tile_logic.hpp"
#include "unit.hpp"
#include "wml_node.hpp"
#include "wml_parser.hpp"
#include "wml_utils.hpp"

namespace game {

namespace {
scenario* current_scenario;

std::map<std::string, std::string> scenario_file_paths;
}

scenario_ptr scenario::get(const std::string& id)
{
	if(scenario_file_paths.empty()) {
		sys::get_unique_filenames_under_dir("data/scenarios", &scenario_file_paths);
	}

	std::map<std::string, std::string>::const_iterator path_itor = scenario_file_paths.find(id + ".cfg");
	ASSERT_LOG(path_itor != scenario_file_paths.end(), "Could not find file for scenario '" << id << "'");

	return scenario_ptr(new scenario(wml::parse_wml_from_file(path_itor->second)));
}

scenario* scenario::current()
{
	return current_scenario;
}

scenario::scenario(wml::const_node_ptr node)
  : title_(node->attr("title")),
    map_(sys::read_file(node->attr("map"))), round_(wml::get_int(node, "round", 1)),
    current_handler_(NULL),
    base_gold_reward_(wml::get_int(node, "base_gold_reward")),
    base_experience_reward_(wml::get_int(node, "base_experience_reward")),
    bonus_gold_reward_(wml::get_int(node, "bonus_gold_reward")),
    bonus_experience_reward_(wml::get_int(node, "bonus_experience_reward"))
{
	FOREACH_WML_CHILD(player_node, node, "player") {
		players_.push_back(player::read(player_node));
		foreach(commander_unit_ptr commander, players_.back()->commanders()) {
			units_[commander->loc()] = commander;
	
			foreach(soldier_unit_ptr soldier, commander->soldiers()) {
				units_[soldier->loc()] = soldier;
			}
		}
	}

	FOREACH_WML_CHILD(objective_node, node, "objective") {
		Condition condition;
		condition.description = objective_node->attr("description");
		if(objective_node->attr("result") == "defeat") {
			defeat_conditions_.push_back(condition);
		} else {
			victory_conditions_.push_back(condition);
		}
	}

	FOREACH_WML_CHILD(events_node, node, "events") {
		for(wml::node::const_attr_iterator i = events_node->begin_attr(); i != events_node->end_attr(); ++i) {
			handlers_[i->first].push_back(game_logic::formula_ptr(new game_logic::formula(i->second, &get_game_functions_symbol_table())));
		}
	}
}

scenario::~scenario()
{
	std::cerr << "DESTROY SCENARIO\n";
	if(current_scenario == this) {
		current_scenario = NULL;
	}
}

void scenario::play()
{
	current_scenario = this;
	foreach(player_ptr player, players_) {
		player->start_scenario();
	}

	deploy_units();
	try {
		for(; !finished(); ++round_) {
			fire("new_round");
			fire(formatter() << "round" << round_);
	
			foreach(player_ptr player, players_) {
				current_player_ = player;
				for(UnitMap::iterator i = units_.begin(); i != units_.end(); ++i) {
					if(player.get() == &i->second->get_commander().owner()) {
						i->second->new_turn();
					}
				}

				fire("new_turn");

				player->play_turn(*this);
				if(finished()) {
					break;
				}
			}
		}
	} catch(player::game_aborted&) {
	}
}

void scenario::draw() const
{
	glPushMatrix();
	glTranslatef(-scroll_x(), -scroll_y(), 0.0);
	map_.draw();
	for(UnitMap::const_iterator i = units_.begin(); i != units_.end(); ++i) {
		i->second->draw();
	}

	glPopMatrix();
}

unit_ptr scenario::get_unit(const hex::location& loc)
{
	UnitMap::iterator i = units_.find(loc);
	if(i != units_.end()) {
		return i->second;
	} else {
		return unit_ptr();
	}
}

const_unit_ptr scenario::get_unit(const hex::location& loc) const
{
	UnitMap::const_iterator i = units_.find(loc);
	if(i != units_.end()) {
		return i->second;
	} else {
		return const_unit_ptr();
	}
}

commander_unit_ptr scenario::get_commander_with_id(const std::string& id) const
{
	if(id.empty()) {
		return commander_unit_ptr();
	}
	
	foreach(player_ptr player, players_) {
		foreach(commander_unit_ptr commander, player->commanders()) {
			if(commander->id() == id) {
				return commander;
			}
		}
	}

	return commander_unit_ptr();
}

void scenario::move_unit(unit_ptr unit, const hex::location& loc)
{
	units_.erase(unit->loc());
	units_[loc] = unit;
	unit->set_loc(loc);
}

void scenario::remove_dead_units()
{
	foreach(player_ptr player, players_) {
		foreach(commander_unit_ptr commander, player->commanders()) {
			commander->remove_dead_soldiers();
		}

		player->remove_dead_commanders();
	}

	UnitMap::iterator i = units_.begin();
	while(i != units_.end()) {
		if(i->second->destroyed()) {
			units_.erase(i++);
		} else {
			++i;
		}
	}
}

void scenario::fire(const std::string& event_name, game_logic::formula_callable_ptr args)
{
	std::map<std::string, std::vector<game_logic::formula_ptr> >::iterator itor = handlers_.find(event_name);
	std::cerr << "FIRE " << event_name << " -> " << (itor == handlers_.end() ? 0 : itor->second.size()) << "\n";
	if(itor == handlers_.end() || itor->second.empty()) {
		return;
	}

	game_logic::formula_callable_ptr callable(this);
	if(args) {
		callable = game_logic::formula_callable_ptr(new game_logic::formula_callable_with_backup(*args, *this));
	} else {
		callable = this;
	}

	foreach(game_logic::formula_ptr& f, itor->second) {
		if(!f) {
			continue;
		}

		current_handler_ = &f;
		variant result = f->execute(*callable);
		execute_command(result);
	}
}

void scenario::execute_command(variant command)
{
	if(command.is_list()) {
		for(int n = 0; n != command.num_elements(); ++n) {
			execute_command(command[n]);
		}
	} else if(command.is_callable()) {
		game_command_callable* cmd = command.try_convert<game_command_callable>();
		if(cmd) {
			cmd->execute(*this);
		}
	}
}

void scenario::deploy_units()
{
	foreach(player_ptr player, players_) {
		foreach(commander_unit_ptr commander, player->commanders()) {
			foreach(soldier_unit_ptr soldier, commander->soldiers()) {
				if(soldier->loc().valid()) {
					continue;
				}

				hex::location result = find_vacant_hex(commander->loc());

				if(result.valid()) {
					ASSERT_LOG(units_.count(result) == 0, "TRYING TO PLACE UNIT IN OCCUPIED LOCATION");
					soldier->set_loc(result);
					units_[result] = soldier;
				}
			}
		}
	}
}

variant scenario::get_value(const std::string& key) const
{
	if(key == "battle") {
		return variant(battle::current());
	} else if(key == "player") {
		return variant(current_player_.get());
	} else if(key == "round") {
		return variant(round_);
	}

	commander_unit_ptr cmd = get_commander_with_id(key);
	if(cmd) {
		return variant(cmd.get());
	}

	foreach(const player_ptr& player, players_) {
		if(player->id() == key) {
			return variant(player.get());
		}
	}

	std::map<std::string, variant>::const_iterator vars_itor = vars_.find(key);
	if(vars_itor != vars_.end()) {
		return vars_itor->second;
	}

	return variant();
}

void scenario::set_value(const std::string& key, const variant& value)
{
	if(key == "result") {
		end_scenario(value.try_convert<scenario_result>());
		return;
	}

	vars_[key] = value;
}

void scenario::remove_current_handler()
{
	if(current_handler_) {
		*current_handler_ = game_logic::formula_ptr();
	}
}

void scenario::end_scenario(const_scenario_result_ptr res)
{
	result_ = res;
}

const_scenario_result_ptr scenario::result() const
{
	return result_;
}

int scenario::performance_rating() const
{
	return 100;
}

bool scenario::add_player_commander(player_ptr player, commander_unit_ptr cmd)
{
	foreach(const hex::location& loc, player->starting_locs()) {
		if(units_.count(loc) == 0) {
			std::cerr << "ADDING COMMANDER AT " << loc.x() << "," << loc.y() << "\n";
			cmd->set_loc(loc);
			player->add_commander(cmd);
			units_[loc] = cmd;
			return true;
		}
	}

	return false;
}

void scenario::add_commander(player_ptr player, commander_unit_ptr cmd)
{
	hex::location loc = find_vacant_hex(cmd->loc());
	ASSERT_LOG(loc.valid() && units_.count(loc) == 0, "COULD NOT FIND HEX TO PLACE UNIT");
	cmd->set_loc(loc);
	player->add_commander(cmd);
	units_[loc] = cmd;

	deploy_units();
}

hex::location scenario::find_vacant_hex(const hex::location& position) const
{
	if(map().is_loc_on_map(position) && units_.count(position) == 0) {
		return position;
	}

	for(int n = 1; n != 25; ++n) {
		std::vector<hex::location> locs;
		hex::get_tile_ring(position, n, locs);
		foreach(const hex::location& loc, locs) {
			if(map().is_loc_on_map(loc) && units_.count(loc) == 0) {
				return loc;
			}
		}
	}

	return hex::location();
}

}
